/*
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Description:
 *   main(), demo handling.
 */

#include "x_main.h"
#include "x_gui.h"
#include "x_saves.h"
#include "doomstat.h"
#include "d_main.h"
#include "e6y.h"
#include "g_game.h"
#include "i_system.h"

#define STARTSIZE 10

extern int xUseSuccess;
extern int xDoneDamage;
extern int xLineCrossed;

dboolean x_longtics = false;
int header_size;
unsigned char *header;
ticcmd_t *tics[MAXPLAYERS];
// 0-indexed, used for manipulating tics[]
int current_tic;
int tic_len[MAXPLAYERS];
int mem_len[MAXPLAYERS]; // for x_moveTics
char savepoint_style;

int mainDoom(int argc, char **argv);

int main(int argc, char **argv)
{
    mainDoom(argc, argv);
    tics[0] = calloc(STARTSIZE, sizeof(ticcmd_t));
    tic_len[0] = mem_len[0] = STARTSIZE;
    header_size = compatibility_level < boom_compatibility_compatibility
      ? 13 : 109;
    header = malloc(header_size);
    G_BeginRecording(header);
    current_tic = -1;
    palette_onbonus = palette_ondamage = palette_onpowers = 1;

    x_goForward(1);
    x_display();
    x_initGui();
    return 0;
}

int x_openDemo(unsigned char *demo_p, size_t size)
{
    int i, players = 0;
    size_t demo_len;
    unsigned char const *tics_p, *demomarker_p;

    tics_p = G_ReadDemoHeader(demo_p, size);
    if (!tics_p)
        goto error;
    if (header_size != (tics_p - demo_p)) {
        free(header);
        header_size = tics_p - demo_p;
        header = malloc(header_size);
    }
    memcpy(header, demo_p, header_size);
    x_clearMapSavepoints();
    x_clearUserSavepoint();
    for (i = 0; i < MAXPLAYERS; i++)
        if (playeringame[i])
            players++;
    if (!players)
        goto error;
    demo_len = size - (tics_p - demo_p);
    for (i = 0; i < MAXPLAYERS; i++) {
        free(tics[i]); // no need to check for NULL
        if (playeringame[i]) {
            mem_len[i] = demo_len / (4 + !!longtics) / players;
            tics[i] = malloc(mem_len[i] * sizeof(ticcmd_t));
            tic_len[i] = 0;
        }
    }
    demomarker_p = x_readTics(tics_p, demo_len);
//  if (!demomarker_p)
//      goto error;

    free(demo_p);
    current_tic = -1;
    gametic = basetic = 0;
    displayplayer = consoleplayer;
    x_goForward(1);
    x_display();
    return true;
  error:
    free(demo_p);
    return false;
}

// modified from G_ReadOneTick
unsigned char const *x_readTics(unsigned char const *start_p, size_t size)
{
    unsigned char const *tic_p = start_p;
    int i, plr;
    ticcmd_t cmd;
    unsigned char at, lowbyte;
    signed char tmp;
    // the green player can be removed with hex-editing
    for (i = 0; i < MAXPLAYERS; i++)
        if (playeringame[i]) {
            plr = i;
            break;
        }
    while (*tic_p != 0x80 && tic_p - start_p < size) {
        cmd.forwardmove = *tic_p++;
        cmd.sidemove = *tic_p++;
        if (!longtics) {
            cmd.angleturn = (at = *tic_p++) << 8;
        } else {
            lowbyte = *tic_p++;
            cmd.angleturn = ((*tic_p++) << 8) + lowbyte;
        }
        cmd.buttons = *tic_p++;
        if (compatibility_level == tasdoom_compatibility) {
            tmp = cmd.forwardmove;
            cmd.forwardmove = cmd.sidemove;
            cmd.sidemove = at;
            cmd.angleturn = cmd.buttons << 8;
            cmd.buttons = tmp;
        }

        tics[plr][tic_len[plr]] = cmd;
        ++tic_len[plr];
        ++plr;
        while (!playeringame[plr = plr % MAXPLAYERS])
            ++plr;
    }
    return *tic_p == 0x80 ? tic_p : NULL;
}

void x_saveDemo(FILE *file)
{
    int i, j = 0, len = tic_len[0];
    ticcmd_t nulltic;

    fwrite(header, 1, header_size, file);
    for (i = 1; i < 4; i++)
        if (tic_len[i] > len)
            len = tic_len[i];
    while (len--) {
        for (i = 0; i < MAXPLAYERS; i++)
            if (j < tic_len[i])
                G_WriteDemoTiccmd(&tics[i][j], file);
            else if (playeringame[i])
                G_WriteDemoTiccmd(&nulltic, file);
        ++j;
    }
    fputc(0x80, file);
    G_WriteDemoFooter(file);
}

// doubles the size of the tic array of displayplayer if it's too small
static dboolean x_checkTicArray(size_t newlength)
{
    size_t size = STARTSIZE;
    if (newlength < mem_len[displayplayer])
        return false; // useless to resize
    while (size < newlength)
        size *= 2;
    tics[displayplayer] = realloc(tics[displayplayer], size * sizeof(ticcmd_t));
    mem_len[displayplayer] = size;
    return true;
}

// moves tics in the array (for copying and deleting)
void x_moveTics(int offset)
{
    int i;
    if (offset < 0) {
        for (i = current_tic; i < tic_len[displayplayer]; i++)
            tics[displayplayer][i] = tics[displayplayer][i - offset];
        return;
    }
    x_checkTicArray(tic_len[displayplayer] + offset);
    for (i = tic_len[displayplayer] - 1; i > current_tic; i--)
        tics[displayplayer][i + offset] = tics[displayplayer][i];
}

void x_makeCmds(void)
{
    ticcmd_t nulltic = {0};
    for (int i = 0; i < MAXPLAYERS; i++) {
        if (!playeringame[i])
            continue;
        if (gametic < tic_len[i])
            netcmds[i] = tics[i][gametic];
        else
            netcmds[i] = nulltic;
    }
}

void x_goForward(int numtics)
{
    int dest, savepoint, savetic;
    if (numtics < 1)
        return;
    dest = numtics + gametic;
    savepoint = x_getSavepointTic(gametic);
    savetic = -1;
    if (dest - savepoint > 140) {
        savetic = dest - 70;
        if (savetic < gametic)
            savetic = gametic;
    }

    while (numtics-- > 0 && gametic < tic_len[displayplayer]) {
        xUseSuccess = xLineCrossed = xDoneDamage = 0;
        if (gametic > 1 && savepoint != gametic)
            if ((savepoint_style == SP_START && leveltime == 1) ||
              (savepoint_style == SP_AUTO && (leveltime == 1 ||
               savetic == gametic)))
                x_makeSavepoint(0);
        x_makeCmds();
        G_Ticker();
        ++current_tic;
        ++gametic;
    }
    /*x_setLinedefCheck(-1);
    x_setSectorCheck(-1);
    x_setThingCheck(-1);*/
}

// if numtics = 0, refreshes current tic
void x_goBack(int numtics)
{
    int desttic, tmp;
    if (numtics < 0)
        return;
    desttic = gametic - numtics;
    desttic = desttic < 1 ? 1 : desttic;
    if (savepoint_style == SP_USER) {
        tmp = x_getSavepointTic(gametic);
        desttic = desttic <= tmp ? tmp + 1 : desttic;
    }
    if (!x_loadSavepoint(desttic)) { // changes gametic
        x_goForward(desttic - gametic);
    } else {
        x_clearMapSavepoints();
        x_clearUserSavepoint();
        gametic = 0;
        current_tic = -1;
        gameaction = ga_nothing; // fix loadgames
        G_ReadDemoHeader(header, header_size);
        x_goForward(desttic);
    }
}

void x_display(void)
{
    D_Display(0);
}

